package service

import (
	"ChdChat-Gin/config"
	"ChdChat-Gin/initialize"
	"ChdChat-Gin/model"
	"ChdChat-Gin/utils"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gomodule/redigo/redis"
	"gorm.io/gorm"
	"log"
	"mime/multipart"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

var (
	ErrLoginBody      = errors.New("user is net exit or account/password wrong")
	ErrFetchUserInfo  = errors.New("failed to fetch user info")
	ErrSystem         = errors.New("internal server error")
	ErrAlreadyContact = errors.New("target is already your contact")
	ErrDataExist      = errors.New("data already exists")
	ErrDatabase       = errors.New("database error")
	//ParamsErr    = errors.New("params error")
)

func LoginVerified(loginBody model.UserAccounts) (model.UserInfos, string, error) {
	var (
		userInfo  model.UserInfos
		account   model.UserAccounts
		tokenInfo model.Signature
	)
	if db := initialize.Conn.
		Model(model.UserAccounts{}).
		Where("account = ? and password = ? and type = ?", loginBody.Account, loginBody.Password, loginBody.Type).
		Find(&account); db.Error != nil || db.RowsAffected < 1 {
		return model.UserInfos{}, "", ErrLoginBody
	}
	if db := initialize.Conn.
		Model(model.UserInfos{}).
		Where("account = ? and type = ?", loginBody.Account, loginBody.Type).
		Find(&userInfo); db.Error != nil || db.RowsAffected < 1 {
		return model.UserInfos{}, "", ErrFetchUserInfo
	}
	// AES encrypts the user ID, returns the key and ciphertext for identity verification
	encoded, aesKey, err := utils.EncryptUserID(userInfo.Account)
	if err != nil {
		log.Printf("utils.EncryptUserID.%v\n", err)
		return model.UserInfos{}, "", ErrSystem
	}
	// Package and save the ciphertext and user information into Redis, return the Redis key
	rdsKey, err := utils.SaveEncryptedData(userInfo, encoded)
	if err != nil {
		log.Printf("utils.SaveEncryptedData.%v\n", err)
		return model.UserInfos{}, "", ErrSystem
	}
	tokenInfo = model.Signature{
		AesKey:  aesKey,
		RdsKey:  rdsKey,
		Type:    userInfo.Type,
		Name:    userInfo.Name,
		Account: userInfo.Account,
	}
	token, err := GenerateToken(tokenInfo)
	if err != nil {
		log.Printf("GenerateToken.%v\n", err)
		return model.UserInfos{}, "", ErrSystem
	}
	return userInfo, token, nil
}

func GenerateToken(sign model.Signature) (string, error) {
	return utils.GenerateTokenUsingHs256(sign)
}

func Logout(rdsKey string) error {
	redisConn := initialize.RedisPool.Get()
	defer func(redisConn redis.Conn) {
		if err := redisConn.Close(); err != nil {
			panic(err)
		}
	}(redisConn)
	if _, err := redisConn.Do("DEL", rdsKey); err != nil {
		log.Printf("redisConn.Do:%v\n", err)
		return ErrSystem
	}
	return nil
}

func RegisterUer(registerBody model.UserAccounts) (int64, error) {
	var (
		db *gorm.DB
		// Used to verify whether the account already exists
		user  model.UserAccounts
		group model.ChatGroups
	)
	if db = initialize.Conn.
		Model(model.UserAccounts{}).
		Where("account = ?", registerBody.Account).
		First(user); db.RowsAffected > 0 {
		log.Printf("db.First:%v\n", ErrDataExist)
		return db.RowsAffected, ErrDataExist
	}
	if db = initialize.Conn.
		Model(model.ChatGroups{}).
		Where("account = ?", registerBody.Account).
		First(group); db.RowsAffected > 0 {
		log.Printf("db.First:%v\n", ErrDataExist)
		return db.RowsAffected, ErrDataExist
	}
	userInfo := &model.UserInfos{
		Account: registerBody.Account,
		Type:    registerBody.Type,
		Name:    "游客",
	}
	if err := initialize.Conn.Transaction(func(tx *gorm.DB) error {
		if tx = initialize.Conn.Create(registerBody); tx.Error != nil {
			return tx.Error
		}
		if tx = initialize.Conn.Create(userInfo); tx.Error != nil {
			return tx.Error
		}
		return nil
	}); err != nil {
		log.Printf("gorm:%v\n", err)
		return 0, ErrDatabase
	}
	return 1, nil
}

func FetchContacts(account string) ([]model.Contacts, []model.UserInfos, []model.ChatGroups, error) {
	var (
		relationship []model.Contacts
		contactList  []model.UserInfos
		groupList    []model.ChatGroups
	)
	if err := initialize.Conn.Transaction(func(tx *gorm.DB) error {
		if tx = initialize.Conn.
			Model(model.Contacts{}).
			Where("user = ?", account).
			Find(&relationship); tx.Error != nil {
			return tx.Error
		}
		if tx = initialize.Conn.
			Model(model.Contacts{}).
			Select("u.account, u.avatar, u.type, u.name ,u.sex, u.age, u.phone, u.email, u.grade, u.address").
			Joins("join `user_infos` as u on contacts.contact = u.account and contacts.user = ? and contacts.type = ?", account, model.CONTACT).
			Find(&contactList); tx.Error != nil {
			return tx.Error
		}
		if tx = initialize.Conn.
			Model(model.Contacts{}).
			Select("g.account, g.avatar, g.name, g.introduce, g.label, g.master").
			Joins("join `chat_groups` as g on contacts.contact = g.account and contacts.user = ? and contacts.type = ?", account, model.GROUP).
			Find(&groupList); tx.Error != nil {
			return tx.Error
		}
		return nil
	}); err != nil {
		log.Printf("gorm.Transaction:%v\n", err)
		return []model.Contacts{}, []model.UserInfos{}, []model.ChatGroups{}, ErrDatabase
	}
	return relationship, contactList, groupList, nil
}
func FetchUserList(account, condition string) ([]model.UserInfos, error) {
	var userList []model.UserInfos
	if db := initialize.Conn.
		Model(model.UserInfos{}).
		Where("account <> ? AND account NOT IN (SELECT contact FROM contacts WHERE user = ?) AND (account = ? OR name like ?)",
			account,
			account,
			condition,
			"%"+condition+"%").
		Find(&userList); db.Error != nil {
		log.Printf("db.Find:%v\n", db.Error)
		return []model.UserInfos{}, ErrDatabase
	}
	return userList, nil
}

func FetchGroupList(account, condition string) ([]model.ChatGroups, error) {
	var groupList []model.ChatGroups
	db := initialize.Conn.
		Model(model.ChatGroups{}).
		Where("account NOT IN (SELECT contact FROM contacts WHERE user = ?) AND (account = ? OR label = ? OR name like ?)",
			account,
			condition,
			condition,
			"%"+condition+"%").
		Find(&groupList)
	if db.Error != nil {
		log.Println(db.Error)
		return []model.ChatGroups{}, ErrDatabase
	}
	return groupList, nil
}

// AddContact Add a contact or group. If you add a group, send an application to the group master
func AddContact(data model.ContactNotices) error {
	var (
		db *gorm.DB

		contact model.Contacts
		notice  model.ContactNotices
	)
	if db = initialize.Conn.
		Model(model.Contacts{}).
		Where("user = ? and contact = ?", data.Account, data.Target).
		Find(&contact); db.Error != nil {
		return ErrAlreadyContact
	}
	if db = initialize.Conn.
		Model(model.ContactNotices{}).
		Where("account = ? and target = ? and is_handle = ?", data.Account, data.Target, data.IsHandle).
		First(&notice); db.RowsAffected > 0 {
		log.Printf("db.First:%v\n", ErrDataExist)
		return ErrDataExist
	}
	db = initialize.Conn.Create(&data)
	if db.Error != nil {
		log.Printf("db.Create:%v", db.Error)
		return ErrDatabase
	}
	return nil
}

// DeleteContact Delete a contact or group. If you delete a group, send notice to group master
func DeleteContact(account string) error {
	// TODO
	return nil
}

func HandleNotice(result model.ContactNotices) error {
	var contact model.Contacts
	if db := initialize.Conn.
		Model(model.Contacts{}).
		Where("user = ? and contact = ?", result.Account, result.Target).
		Find(&contact); db.Error != nil {
		return ErrAlreadyContact
	}
	if err := initialize.Conn.Transaction(func(tx *gorm.DB) error {
		if tx = initialize.Conn.
			Model(model.ContactNotices{}).
			Where("id = ?", result.Id).Update("is_handle", result.IsHandle); tx.Error != nil {
			return tx.Error
		}
		if result.IsHandle == model.PASS {
			relations := []model.Contacts{
				{
					Type:    result.Type,
					User:    result.Account,
					Contact: result.Target,
				},
			}
			if result.Type == model.ADDCONTACT {
				alter := model.Contacts{
					Type:    result.Type,
					User:    result.Target,
					Contact: result.Account,
				}
				relations = append(relations, alter)
			}
			if tx = initialize.Conn.Create(&relations); tx.Error != nil {
				return tx.Error
			}
		}
		return nil
	}); err != nil {
		log.Printf("db.Transaction:%v\n", err)
		return ErrDatabase
	}
	return nil
}

func FetchContactNotices(account string) ([]model.ContactNotices, []model.ContactNotices, error) {
	var (
		noticeList     []model.ContactNotices
		contactNotices []model.ContactNotices
		groupNotices   []model.ContactNotices
	)
	if db := initialize.Conn.
		Model(model.ContactNotices{}).
		Where("account = ? or target = ?", account, account).
		Find(&noticeList); db.Error != nil {
		log.Printf("db.Find:%v\n", db.Error)
		return []model.ContactNotices{}, []model.ContactNotices{}, ErrDatabase
	}
	for _, notice := range noticeList {
		if notice.Type == model.ADDCONTACT {
			contactNotices = append(contactNotices, notice)
		} else {
			groupNotices = append(groupNotices, notice)
		}
	}
	return contactNotices, groupNotices, nil
}

func UpdateUserInfo(useInfo model.UserInfos) error {
	if db := initialize.Conn.Model(model.UserInfos{}).
		Where("account = ?", useInfo.Account).
		Save(useInfo); db.Error != nil {
		log.Printf("db.Save:%v\n", db.Error)
		return ErrDatabase
	}
	return nil
}

// FetchAvatarSrc fetch system default avatar source
func FetchAvatarSrc() ([]string, error) {
	source := make([]string, 0, 10)
	srcPath := config.AVATARSRCPATH

	dir, err := os.ReadDir(srcPath)
	if err != nil {
		log.Printf("os.ReadDir:%v\n", err)
		return source, ErrSystem
	}
	for _, file := range dir {
		source = append(source, file.Name())
	}
	return source, nil
}

func UploadAvatar(file *multipart.FileHeader, c *gin.Context) (string, error) {
	fmt.Println("file name", file.Filename)
	extension := strings.ToLower(filepath.Ext(file.Filename))
	fmt.Println("ext", extension)
	// Check whether the file format is valid
	if _, ok := model.AllowFileExt[extension]; !ok {
		return "", errors.New("illegal file extension")
	}
	// Generate file name with UnixNano time
	fileName := strconv.FormatInt(time.Now().UnixNano(), 10)
	// Save avatar file
	savePath := config.AVATARDIYPATH + fileName + extension
	if err := c.SaveUploadedFile(file, savePath); err != nil {
		log.Printf("gin.Context.SaveUploadedFile:%v\n", err)
		return "", errors.New("save picture error")
	}
	// return avatar url to user
	str := []string{config.CLOUDAVATARPATH, "diy/", fileName, extension}
	avatarURL := strings.Join(str, "")
	fmt.Println("url", avatarURL)
	return avatarURL, nil
}
